﻿<script setup lang="ts" name="UploadModal">
import { isFunction } from '@/utils/is'
import { uploadApi } from '@/api/upload'
import { FileItem } from './type'
import { errorMessage, warnMessage } from '@/utils/message'
import { transformI18n } from '@/plugins/i18n'
import { checkImgType, getBase64WithFile, isImgTypeByName } from './helper'
import { buildUUID } from '@/utils/uuid'
import { cloneDeep } from 'lodash-es'
import { useResizeObserver, useDebounceFn } from '@vueuse/core'
import { storeToRefs } from 'pinia'
import { useSystemConfigStore } from '@/store/storage/systemConfigStore'
import { downloadByUrl } from '@/utils/file/download'
const emit = defineEmits(['save'])

const systemConfigStore = useSystemConfigStore()
const { systemConfig } = storeToRefs(systemConfigStore)
const dialogVisible = ref(false)

const props = defineProps({
  modalType: { type: String },
  config: { type: Object },
  data: {
    type: Array,
    default: () => {
      return []
    }
  }
})

//   是否正在上传
const isUploadingRef = ref(false)

// 打开弹窗
const handleOpenDialog = () => {
  dialogVisible.value = true
  setTimeout(() => {
    getTableHeight()
  }, 200)
}
// 关闭弹窗
const handleCloseDialog = () => {
  if (isUploadingRef.value) {
    warnMessage(transformI18n('text.textUploadWait', true))
    return false
  } else {
    dialogVisible.value = false
    fileListRef.value = []
    imagePreviewList.value = []
  }
}
// 子组件将方法暴露给父组件
defineExpose({ handleOpenDialog })

// 提示文本组装
const helpText = computed(() => {
  if (props.config.helpText) {
    return props.config.helpText
  }
  const helpTexts = []
  if (props.config.accept) {
    helpTexts.push(transformI18n('text.textAccept', true, [props.config.accept]))
  }
  if (props.config.maxSize) {
    helpTexts.push(transformI18n('text.textMaxSize', true, [props.config.maxSize]))
  }
  if (props.config.maxNumber) {
    helpTexts.push(transformI18n('text.textMaxNumber', true, [props.config.maxNumber]))
  }
  return helpTexts.join('，')
})

// 上传失败的数据
const errorList = ref([])
// 上传成功的数据
const successList = ref([])

// 上传弹窗的列表数据
const fileListRef = ref<FileItem[]>([])
// 上传弹窗的数据中图片类型的预览数据
const imagePreviewList = ref([])
// 选择文件后上传前的操作
const beforeUpload = (file: File) => {
  const { size, name } = file
  const maxSize = props.config.maxSize
  // 设置最大值，则判断
  if (maxSize) {
    if (maxSize > 0 && file.size / 1024 / 1024 >= maxSize) {
      errorMessage(transformI18n('text.textMaxSizeMultiple', true, [maxSize]))
      return false
    }
  }
  let fileSize = ''
  if (size > 0) {
    if (size / 1024 / 1024 > 1) {
      fileSize = Math.floor(size / 1024 / 1024 * 100) / 100 + 'MB'
    } else {
      fileSize = Math.floor(size / 1024 * 100) / 100 + 'KB'
    }
  } else {
    fileSize = '0KB'
  }
  const commonItem = {
    uuid: buildUUID(),
    file,
    size: fileSize,
    name,
    percent: 0,
    status: 1, // 1:等待上传，2：正在上传，3：上传成功，4：上传失败
    type: name.split('.').pop()
  }
  // 生成图片缩略图
  if (checkImgType(file)) {
    // beforeUpload，如果异步会调用自带上传方法
    // file.thumbUrl = await getBase64(file);
    getBase64WithFile(file).then(({ result: thumbUrl }) => {
      const fileList = cloneDeep(fileListRef.value)
      fileList.push({
        thumbUrl,
        ...commonItem
      })
      imagePreviewList.value.push(thumbUrl)
      fileListRef.value = sortImagePreviewIndex(fileList, 'thumbUrl')
    })
  } else {
    const newFileList = [...unref(fileListRef), commonItem]
    fileListRef.value = sortImagePreviewIndex(newFileList, 'thumbUrl')
    let index = 0
    fileListRef.value.forEach((item) => {
      if (item.thumbUrl) {
        item.index = index
        index++
      } else {
        item.index = undefined
      }
    })
  }
  return false
}
// 开始上传按钮事件
const handleUpload = async () => {
  const maxNumber = props.config.maxNumber
  if (maxNumber) {
    if ((fileListRef.value.length + previewFileListRef.value.length) > maxNumber) {
      return warnMessage(transformI18n('text.textMaxNumber', true, [maxNumber]))
    }
  }
  try {
    errorList.value = []
    isUploadingRef.value = true
    // 只上传不是成功状态的
    const uploadFileList = fileListRef.value.filter((item) => item.status !== 3) || []
    const data = await Promise.all(
      uploadFileList.map((item) => {
        return uploadByItem(item)
      })
    )
    isUploadingRef.value = false
    data.forEach((item) => {
      if (item.success) {
        successList.value.push(item)
      }
    })
    // 生产环境:抛出错误
    errorList.value = data.filter((item: any) => !item.success)
    // if (errorList.length > 0) throw errorList
  } catch (e) {
    isUploadingRef.value = false
    throw e
  }
}
// 单个文件上传调用上传接口
const uploadByItem = async (item) => {
  if (!uploadApi || !isFunction(uploadApi)) {
    return warnMessage('upload api must exist and be a function')
  }
  try {
    item.status = 2
    const result: any = await uploadApi(
      { name: 'file', file: item.file, filename: item.name },
      {
        headers: {
          'Content-Type': 'multipart/form-data'
        },
        onUploadProgress: (progressEvent: ProgressEvent) => {
          const complete = ((progressEvent.loaded / progressEvent.total) * 100) | 0
          item.percent = complete
        },
        config: { showLoading: false, mockEnable: true }
      })
    if (result.status) {
      item.status = 3
      item.responseData = result.data
      return {
        success: true,
        error: null
      }
    } else {
      item.status = 4
      return {
        success: false,
        error: result.info
      }
    }
  } catch (e) {
    console.log(e)
    item.status = 4
    return {
      success: false,
      error: e
    }
  }
}
// 上传弹窗的图片预览索引整理
const sortImagePreviewIndex = (data, key) => {
  let index = 0
  data.forEach((item) => {
    if (item[key]) {
      item.index = index
      index++
    } else {
      item.index = undefined
    }
  })
  return data
}
// 上传弹窗的删除事件
const handleFileRemove = (record: FileItem) => {
  const fileList = cloneDeep(fileListRef.value)
  const index = fileList.findIndex((item) => item.uuid === record.uuid)
  index !== -1 && fileList.splice(index, 1)
  fileListRef.value = sortImagePreviewIndex(fileList, 'thumbUrl')
  const imageIndex = imagePreviewList.value.findIndex((item) => record.thumbUrl && item === record.thumbUrl)
  imageIndex !== -1 && imagePreviewList.value.splice(index, 1)
  if (record.status === 3) {
    successList.value.splice(0, 1)
  }
  if (record.status === 4) {
    errorList.value.splice(0, 1)
  }
  console.log(fileListRef.value)
}
// 上传弹窗的保存按钮点击事件，保存上传成功的数据
const handleOk = () => {
  const maxNumber = props.config.maxNumber
  if (maxNumber) {
    if ((successList.value.length + previewFileListRef.value.length) > maxNumber) {
      return warnMessage(transformI18n('text.textMaxNumber', true, [maxNumber]))
    }
  }
  if (isUploadingRef.value) {
    return warnMessage(transformI18n('text.textUploadWait', true))
  }
  const fileList = []

  for (const item of fileListRef.value) {
    const { status, responseData } = item
    if (status === 3 && responseData) {
      fileList.push({ name: responseData.name, url: responseData.url })
    }
  }
  // 更新数据，需要增加新保存的数据
  emit('save', previewFileListRef.value.concat(fileList))
  // 存在一个上传成功的即可保存
  if (fileList.length <= 0) {
    return warnMessage(transformI18n('text.textUploadWait', true))
  }
  handleCloseDialog()
}

// 预览弹窗的数据（保存的上传成功的数据）
const previewFileListRef = computed(() => {
  const previewFileList = sortPreviewImageIndex(props.data)
  getImagePreview(previewFileList)
  return previewFileList
})
// 预览弹窗的图片数据（上传成功的数据的图片预览数据）
const imageUploadedPreviewList = ref([])

// 预览弹窗的图片预览数据整理
const getImagePreview = (data) => {
  if (data.length > 0) {
    data.forEach((item: any) => {
      if (isImgTypeByName(item.name)) {
        imageUploadedPreviewList.value.push(item.url)
      }
    })
  }
}
// 预览弹窗获取图片类型的数据的索引
const sortPreviewImageIndex = (data) => {
  let index = 0
  data.forEach((item: any) => {
    if (isImgTypeByName(item.name)) {
      item.index = index
      index++
    } else {
      item.index = undefined
    }
  })
  return data
}
// 预览弹窗的删除事件
const handlePreviewRemove = (record) => {
  const fileList = cloneDeep(previewFileListRef.value)
  const index = fileList.findIndex((item) => item.url === record.url)
  index !== -1 && fileList.splice(index, 1)
  const newFileList = sortPreviewImageIndex(fileList)
  emit('save', newFileList)
  const imageIndex = imageUploadedPreviewList.value.findIndex((item) => record.index !== undefined && item === record.url)
  imageIndex !== -1 && imageUploadedPreviewList.value.splice(index, 1)
  if (record.status === 3) {
    successList.value.splice(0, 1)
  }
  if (record.status === 4) {
    errorList.value.splice(0, 1)
  }
  console.log(previewFileListRef.value)
}
// 预览弹窗中下载事件
const handleDownload = (record) => {
  downloadByUrl({ url: record.url })
}

// 最大化效果
const fullscreen = ref(false)
// 最大化点击事件
const maxSizeHandle = () => {
  fullscreen.value = !fullscreen.value
}
// 监听最大化事件
watch(
  () => fullscreen.value,
  () => {
    getTableHeight()
  }
)
const tableHeight = ref(0)
// 监听元素尺寸变化
useResizeObserver(
  document.body,
  useDebounceFn(() => {
    getTableHeight()
  }, 200)
)
const modalHeader = ref(null)
const modalTips = ref(null)
const modalFooter = ref(null)
// 获取当前的表格的最大高度
const getTableHeight = () => {
  if (dialogVisible.value) {
    const rect = document.body.getBoundingClientRect()
    if (props.modalType === 'upload') {
      if (fullscreen.value) {
        tableHeight.value = rect.height - modalHeader.value.offsetHeight - modalTips.value.offsetHeight - modalFooter.value.offsetHeight - 15 * 2 - systemConfig.value.space * 2 - 2
      } else {
        tableHeight.value = rect.height * 0.7 - modalHeader.value.offsetHeight - modalTips.value.offsetHeight - modalFooter.value.offsetHeight - 15 * 2 - systemConfig.value.space * 2 - 2
      }
    } else {
      if (fullscreen.value) {
        tableHeight.value = rect.height - modalHeader.value.offsetHeight - modalFooter.value.offsetHeight - 15 * 2 - systemConfig.value.space * 2 - 2
      } else {
        tableHeight.value = rect.height * 0.7 - modalHeader.value.offsetHeight - modalFooter.value.offsetHeight - 15 * 2 - systemConfig.value.space * 2 - 2
      }
    }
  }
}
</script>

<template>
  <el-dialog v-model="dialogVisible" width="800px" append-to-body destroy-on-close :fullscreen="fullscreen" :show-close="false" :before-close="handleCloseDialog">
    <template #header>
      <div class="modal-header" ref="modalHeader">
        <div class="header-title">
          {{ props.modalType === 'upload' ? $t('moduleTitle.moduleTitleUpload') : $t('moduleTitle.moduleTitlePreview') }}
        </div>
        <el-tooltip :content="fullscreen ? $t('buttons.buttonRestoreDown') : $t('buttons.buttonMaximize')" placement="bottom">
          <el-button size="small" @click="maxSizeHandle"><svg-icon :icon="fullscreen ? 'bi:fullscreen-exit' : 'bi:fullscreen'"></svg-icon></el-button>
        </el-tooltip>
        <el-tooltip :content="$t('buttons.buttonClose')" placement="bottom">
          <el-button size="small" @click="handleCloseDialog"><svg-icon icon="ep:close"></svg-icon></el-button>
        </el-tooltip>
      </div>
    </template>
    <div class="upload-tips" v-if="props.modalType === 'upload'" ref="modalTips">
      <div class="tips-panel">
        <el-alert type="info" v-if="helpText" :title="helpText" :closable="false" show-icon></el-alert>
      </div>
      <el-upload :show-file-list="false" :accept="props.config.accept" :multiple="props.config.multiple" :before-upload="beforeUpload">
        <el-button type="primary">{{ $t('buttons.buttonSelectFile') }}</el-button>
      </el-upload>
    </div>
    <el-table v-if="props.modalType === 'upload'" border :data="fileListRef" style="width: 100%" :max-height="tableHeight">
      <el-table-column v-if="props.config.thumb" prop="thumbUrl" :label="$t('text.textThumbnail')" width="120" align="center">
        <template #default="{ row }">
          <div class="thumb-image" v-if="row.thumbUrl">
            <el-image :src="row.thumbUrl" :preview-src-list="imagePreviewList" :initial-index="row.index" fit="cover"></el-image>
            <div class="image-mask">
              <svg-icon icon="ri:eye-line"></svg-icon>
              {{ $t('buttons.buttonPreview') }}
            </div>
          </div>
        </template>
      </el-table-column>
      <el-table-column prop="name" :label="$t('text.textFileName')" sortable>
        <template #default="{ row }">
          <div class="file-name">
            <p>{{ row.name }}</p>
            <el-progress :percentage="row.percent" :status="row.status === 3 ? 'success' : row.status === 4 ? 'exception' : ''" />
          </div>
        </template>
      </el-table-column>
      <el-table-column prop="size" :label="$t('text.textFileSize')" width="120" align="center" />
      <el-table-column prop="status" :label="$t('text.textStatus')" width="160" align="center">
        <template #default="{ row }">
          <div class="file-status">
            <el-tag type="info" v-if="row.status === 1">{{ $t('text.textWaitingUpload') }}</el-tag>
            <el-tag type="success" v-if="row.status === 2">{{ $t('text.textUploading') }}</el-tag>
            <el-tag type="success" v-if="row.status === 3">{{ $t('text.textUploadedSuccessfully') }}</el-tag>
            <el-tag type="danger" v-if="row.status === 4">{{ $t('text.textUploadFailed') }}</el-tag>
          </div>
        </template>
      </el-table-column>
      <el-table-column prop="operate" :label="$t('text.textOperate')" width="100" align="center">
        <template #default="{ row }">
          <div class="file-operate">
            <el-link :underline="false" type="danger" @click="handleFileRemove(row)">
              {{ $t('buttons.buttonRemove') }}
            </el-link>
          </div>
        </template>
      </el-table-column>
    </el-table>
    <el-table v-else border :data="previewFileListRef" style="width: 100%" :max-height="tableHeight">
      <el-table-column v-if="props.config.thumb" prop="url" :label="$t('text.textThumbnail')" width="120" align="center">
        <template #default="{ row }">
          <div class="thumb-image" v-if="row.url">
            <el-image :src="row.url" :preview-src-list="imageUploadedPreviewList" :initial-index="row.index" fit="cover"></el-image>
            <div class="image-mask">
              <svg-icon icon="ri:eye-line"></svg-icon>
              {{ $t('buttons.buttonPreview') }}
            </div>
          </div>
        </template>
      </el-table-column>
      <el-table-column prop="name" :label="$t('text.textFileName')" sortable>
        <template #default="{ row }">
          <div class="file-name">
            <p>{{ row.name }}</p>
          </div>
        </template>
      </el-table-column>
      <el-table-column prop="operate" :label="$t('text.textOperate')" width="100" align="center">
        <template #default="{ row }">
          <div class="file-operate">
            <el-link :underline="false" type="primary" @click="handleDownload(row)">
              {{ $t('buttons.buttonDownload') }}
            </el-link>
            <el-link :underline="false" type="danger" @click="handlePreviewRemove(row)">
              {{ $t('buttons.buttonRemove') }}
            </el-link>
          </div>
        </template>
      </el-table-column>
    </el-table>
    <template #footer>
      <span class="modal-footer" ref="modalFooter">
        <el-button @click="handleCloseDialog">{{ $t('buttons.buttonCancel') }}</el-button>
        <el-button type="primary" @click="handleUpload" v-if="props.modalType === 'upload'" :disabled="fileListRef.length === successList.length">
          {{ errorList.length > 0 ?
            $t('buttons.buttonReUploadFailFile') : $t('buttons.buttonStartUpload') }}
        </el-button>
        <el-button type="primary" @click="handleOk" v-if="props.modalType === 'upload'" :disabled="successList.length === 0">{{ $t('buttons.buttonSave') }}</el-button>
      </span>
    </template>
  </el-dialog>
</template>

<style scoped lang="scss">
.modal-header {
  display: flex;
  margin: -15px calc(0px - var(--el-space));
  padding: 15px var(--el-space);

  .header-title {
    flex: 1;
    height: 24px;
    line-height: 24px;
  }

  .el-button {
    margin-left: 0;
    background: none !important;
    border: none !important;

    &:hover,
    &:focus {
      background: none;
    }
  }
}

.modal-footer {
  display: inline-flex;
  overflow: hidden;
}

.upload-tips {
  display: flex;
  padding-bottom: 10px;

  .tips-panel {
    flex: 1;

    .el-alert {
      display: inline-flex;
      width: auto;
      padding-top: 7px;
      padding-bottom: 7px;
      border: none;

      :deep(.el-alert__content) {
        line-height: 0;

        .el-alert__title {
          font-size: 12px;
        }
      }
    }
  }
}

.thumb-image {
  position: relative;
  overflow: hidden;
  line-height: 0;

  .image-mask {
    position: absolute;
    top: 0;
    left: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    color: var(--el-color-white);
    background: var(--el-overlay-color-lighter);
    opacity: 0;
    transition: opacity 0.28s;
    pointer-events: none;

    .el-icon {
      margin-right: 5px;
    }
  }

  &:hover {
    .image-mask {
      opacity: 1;
    }
  }
}

.file-operate {
  .el-link {
    margin: 3px;
  }
}
</style>
